package com.virjar.ucrack.apphook;

import android.app.Activity;
import android.util.Log;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.taobao.android.dexposed.DexposedBridge;
import com.taobao.android.dexposed.XC_MethodHook;
import com.virjar.hermes.hermesagent.hermes_api.APICommonUtils;
import com.virjar.hermes.hermesagent.hermes_api.AgentCallback;
import com.virjar.hermes.hermesagent.hermes_api.EmbedHermes;
import com.virjar.hermes.hermesagent.hermes_api.aidl.InvokeRequest;
import com.virjar.hermes.hermesagent.hermes_api.aidl.InvokeResult;
import com.virjar.ucrack.plugin.LogUtil;
import com.virjar.ucrack.plugin.hotload.XposedHotLoadCallBack;
import com.virjar.xposed_extention.ForceFiledViewer;
import com.virjar.xposed_extention.SharedObject;
import com.virjar.xposed_extention.SingletonXC_MethodHook;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class DianpingHook implements XposedHotLoadCallBack {
    private static volatile boolean entried = false;
    private static ClassLoader classLoader = null;


    @Override
    public void onXposedHotLoad() {

        EmbedHermes.bootstrap(new DianpingWrapper());

//        DexposedBridge.hookAllConstructors(Activity.class, new XC_MethodHook() {
//
//            @Override
//            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//                super.afterHookedMethod(param);
//                Log.i("dianping_hook", param.thisObject.getClass().getName() + " called");
//                classLoader = param.thisObject.getClass().getClassLoader();
//                if (entried) {
//                    return;
//                }
//                hookEntry();
//                entried = true;
//            }
//        });
    }


    public static void hookEntry() throws ClassNotFoundException {
        Class<?> aClass = classLoader.loadClass("com.dianping.model.FeedDetail");
        DexposedBridge.hookAllConstructors(aClass, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Log.i("dianping_hook", "feedDetail instance  trace：" + LogUtil.getOwnerThreadTrack());
            }
        });
        DexposedBridge.findAndHookMethod(classLoader.loadClass("com.dianping.archive.e"),
                "a", "com.dianping.archive.c", new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        Log.i("dianping_hook", "结果为：" + JSONObject.toJSONString(ForceFiledViewer.toView(param.getResult())));
                    }
                });

        DexposedBridge.hookAllConstructors(classLoader.loadClass("com.dianping.nvnetwork.NVDefaultNetworkService$b"), new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Log.i("dianping_hook", "begin sending data: " + JSONObject.toJSONString(ForceFiledViewer.toView(param.args[1])));
                Log.i("dianping_hook", "statck trace:" + LogUtil.getOwnerThreadTrack());
            }
        });

//        DexposedBridge.findAndHookMethod(classLoader.loadClass("okhttp3.OkHttpClient"), "proxySelector"
//                , XC_MethodReplacement.returnConstant(ProxySelector.getDefault()));

        DexposedBridge.findAndHookMethod(classLoader.loadClass("com.dianping.dataservice.mapi.impl.DefaultMApiService"),
                "exec", "com.dianping.dataservice.mapi.f", "com.dianping.dataservice.e", new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.i("dianping_hook", "sent data request type：:" + param.args[0].getClass() + " request data:" + JSONObject.toJSONString(ForceFiledViewer.toView(param.args[0])));
                    }
                });

        DexposedBridge.findAndHookMethod(classLoader.loadClass("com.dianping.hotel.review.a"), "a", int.class, int.class, String.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                Log.i("dianping_hook", "评论数据构造请求：" + param.args[0] + "   " + param.args[1] + " " + param.args[2] +
                        "构造结构：" + JSONObject.toJSONString(ForceFiledViewer.toView(param.getResult())));
            }
        });
    }

    @Override
    public boolean needHook(XC_LoadPackage.LoadPackageParam loadPackageParam) {
        return loadPackageParam.packageName.equals("com.dianping.v1");
    }

    static class DianpingWrapper implements AgentCallback {
        private static Map<Object, Object> queryResult = Maps.newConcurrentMap();
        private static Map<Object, Object> lockes = Maps.newConcurrentMap();

        @Override
        public boolean needHook(XC_LoadPackage.LoadPackageParam loadPackageParam) {
            return true;
        }

        @Override
        public InvokeResult invoke(InvokeRequest invokeRequest) {
            String shopid = invokeRequest.getString("shopid");
            if (StringUtils.isBlank(shopid)) {
                return InvokeResult.failed("the param {shopid} is not presented");
            }
            if (classLoader == null) {
                classLoader = SharedObject.loadPackageParam.classLoader;
            }
            Log.i("weijia", "获取到查询店铺的请求：" + shopid);
            try {
                //step1 组装请求对象
                Class<?> aClass = classLoader.loadClass("com.dianping.apimodel.HotelreviewpagelistHotelm");
                Object request = XposedHelpers.newInstance(aClass);
                //shop id
                XposedHelpers.setObjectField(request, "b", NumberUtils.toInt(shopid));
                //filter id
                XposedHelpers.setObjectField(request, "e", 800);
                //extrareviewids TODO 确认这个参数来源
                XposedHelpers.setObjectField(request, "g", "[{\"platform\":1,\"reviewId\":\"463798225\"},{\"platform\":1,\"reviewId\":\"468097967\"}]");
                //start
                XposedHelpers.setObjectField(request, "f", 0);

                Class<?> cacheTypeClass = classLoader.loadClass("com.dianping.dataservice.mapi.c");
                Object disableCacheType = XposedHelpers.getStaticObjectField(cacheTypeClass, "b");
                //禁止使用缓存
                XposedHelpers.setObjectField(request, "n", disableCacheType);
                Object mApiRequest = XposedHelpers.callMethod(request, "n_");


                Log.i("weijia", "请求组装成功：" + JSONObject.toJSONString(ForceFiledViewer.toView(mApiRequest)));
                //step2 获取发送器
                Object applicationInstance = XposedHelpers.callStaticMethod(classLoader.loadClass("com.dianping.app.DPApplication"), "instance");
                Object mapiService = XposedHelpers.callMethod(applicationInstance, "mapiService");

                Log.i("weijia", "发送器获取成功");
                //step3 构造接收器
                Object netWorkCallback = createNetWorkCallback(mApiRequest);

                Log.i("weijia", "网络接收器获取成功：" + netWorkCallback);
                Object lock = new Object();
                lockes.put(mApiRequest, lock);

                //step4 发送数据
                XposedHelpers.callMethod(mapiService, "exec", mApiRequest, netWorkCallback);

                Log.i("weijia", "数据发送成功");
                try {
                    //step 5 等待结果返回
                    synchronized (lock) {
                        //本身是一个异步请求，这里等待6s，等待异步的结果，异步转同步
                        lock.wait(6500);
                        Object remove = queryResult.remove(mApiRequest);
                        if (remove == null) {
                            APICommonUtils.requestLogW(invokeRequest, "请求超时，返回TimeOut");
                            return InvokeResult.failed("timeOut");
                        }
                        Log.i("weijia", "数据返回");
                        if (remove instanceof String) {
                            return InvokeResult.failed((String) remove);
                        }

                        Object dPObject = XposedHelpers.callMethod(remove, "a");
                        if (dPObject == null) {
                            return InvokeResult.failed("返回内容为空，系统错误");
                        }
                        Class<?> dpObjectclass = classLoader.loadClass("com.dianping.archive.DPObject");
                        if (!dpObjectclass.isAssignableFrom(dPObject.getClass())) {
                            return InvokeResult.failed("请求失败，请稍后再试");
                        }

                        //step6 结果返回，对数据内容解码
                        if (decoder == null) {
                            Log.i("weijia", "创建解码器");
                            decoder = createDecodeFactoryHandler();
                        }
                        Object result = XposedHelpers.callMethod(dPObject, "a", decoder);
                        Log.i("weijia", "解码成功：" + JSONObject.toJSONString(ForceFiledViewer.toView(result)));
                        return InvokeResult.success(ForceFiledViewer.toView(result), SharedObject.context);
                    }
                } catch (InterruptedException e) {
                    APICommonUtils.requestLogW(invokeRequest, "等待响应超时", e);
                    return InvokeResult.failed("timeOut");
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                return InvokeResult.failed(throwable.getMessage());
            }
        }

        private static Object decoder;

        private static Object createDecodeFactoryHandler() throws ClassNotFoundException {
            DecodingFactoryInvocationHandler networkCallbackInvocationHandler = new DecodingFactoryInvocationHandler();
            Class callbackInterface = classLoader.loadClass("com.dianping.archive.c");
            if (callbackInterface == null) {
                throw new IllegalStateException("can not find network callback interface class,the apk maybe upgraded");
            }
            return Proxy.newProxyInstance(callbackInterface.getClassLoader(), new Class[]{callbackInterface},
                    networkCallbackInvocationHandler);
        }

        static class DecodingFactoryInvocationHandler implements InvocationHandler {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getDeclaringClass() != classLoader.loadClass("com.dianping.archive.c")) {
                    //只处理callback的方法，其他方法可能是object的
                    return method.invoke(this, args);
                }
                Class<?> feedListClass = classLoader.loadClass("com.dianping.model.HotelReviewFeedList");
                if (method.getName().equals("createArray")) {
                    return Array.newInstance(feedListClass, (Integer) args[0]);
                }
                if (method.getName().equals("createInstance")) {
                    int paramI = (int) args[0];
                    if (paramI == 1365) {
                        return feedListClass.newInstance();
                    }
                    return XposedHelpers.newInstance(feedListClass, false);
                }
                return null;
            }
        }


        class NetworkCallbackInvocationHandler implements InvocationHandler {
            private Object requestBean;

            NetworkCallbackInvocationHandler(Object requestBean) {
                this.requestBean = requestBean;
            }

            @Override
            public Object invoke(Object proxy, Method method, Object[] param) throws Throwable {
                if (method.getDeclaringClass() != classLoader.loadClass("com.dianping.dataservice.e")) {
                    //只处理callback的方法，其他方法可能是object的
                    return method.invoke(this, param);
                }

                //    void onRequestFailed(T t, R r);
                //
                //    void onRequestFinish(T t, R r);
                if (method.getName().equals("onRequestFailed")) {
                    queryResult.put(requestBean, "failed");
                } else {
                    queryResult.put(requestBean, param[1]);
                }
                Object lock = lockes.remove(requestBean);
                if (lock != null) {
                    synchronized (lock) {
                        lock.notify();
                    }
                }
                return null;
            }
        }

        private Object createNetWorkCallback(Object requestBean) throws ClassNotFoundException {
            NetworkCallbackInvocationHandler networkCallbackInvocationHandler = new NetworkCallbackInvocationHandler(requestBean);
            Class callbackInterface = classLoader.loadClass("com.dianping.dataservice.e");
            if (callbackInterface == null) {
                throw new IllegalStateException("can not find network callback interface class,the apk maybe upgraded");
            }
            return Proxy.newProxyInstance(callbackInterface.getClassLoader(), new Class[]{callbackInterface},
                    networkCallbackInvocationHandler);
        }

        @Override
        public void onXposedHotLoad() {
            XposedBridge.hookAllConstructors(Activity.class, new SingletonXC_MethodHook() {

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    Log.i("dianping_hook", param.thisObject.getClass().getName() + " called");
                    classLoader = param.thisObject.getClass().getClassLoader();
                }
            });
        }
    }
}
